package Cinema.CinemaEntities;

import java.util.ArrayList;
import java.util.Date;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import Utils.XmlParseHelper;
import Cinema.DataLayer.IXmlParsable;
import Cinema.DataLayer.XmlDefines;
import Exceptions.IllegalSeatsFormat;
import Exceptions.SeatAlreadyFree;
import Exceptions.WrongXMLElement;



/**
* Represents a show in Cinema system.
 * @author The Fuchtzeinsoft Team.
 *
 */
public class Show implements IXmlParsable, IShow{

	private Date date;				// The data & time of the show
	private String movieName;		// The movie name of the show.
	private String movieGenre;

	protected Boolean[][] seats;		// Represent the seats state in the show.
	private Theater theater;

	static long counter = 0;
	protected long myID;

	private Movie myMovie;
	
	private int childPrice;
	private int adultPrice;
	

	/**
	 * Getter for the show id.
	 * @return The show id.
	 */
	public long getId(){
		return myID;
	}

	/**
	 * Setter for the counter.
	 */
	public static void setCounter(long value){
		counter = value;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#saveMovie(Cinema.CinemaEntities.Movie)
	 */
	@Override
	public void saveMovie(Movie movie){
		this.movieName = movie.getName();
		this.movieGenre = movie.getGenre();
		myMovie = movie;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#getMovie()
	 */
	@Override
	public Movie getMovie(){
		return myMovie;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#getTheater()
	 */
	@Override
	public Theater getTheater(){
		return theater;
	}

	/**
	 * Returns the ticket price of one adult.
	 * @return The ticket price of one adult.
	 */
	public int getAdultTicketPrice()
	{
		return adultPrice;
	}

	/**
	 * Returns the ticket price of one child.
	 * @return The ticket price of one child.
	 */
	public int getChildTicketPrice()
	{
		return childPrice;
	}

	/**
	 * Constructor of the Show class
	 * @param date - The time and date of the show.
	 * @param theater - The theater of the show.
	 * @param movieName - The name of the movie.
	 */
	public Show(Date date,Theater theater, String movieName, String movieGenre, int adultPrice, int childPrice){
		super();
		this.myID = counter;
		counter++;
		this.theater = theater;
		this.date = date;
		this.movieName = movieName;
		this.movieGenre = movieGenre;
		this.adultPrice = adultPrice;
		this.childPrice = childPrice;
		this.seats = new Boolean[theater.getRowAmount()][];
		for (int i = 0; i < seats.length; i++){				// Initialize all seats to be empty
			seats[i] = new Boolean[theater.getRowSeatsAmount()];
			for (int j = 0; j < seats[i].length; j++)
				seats[i][j] = false;
		}
	}

	/**
	 * Constructor of the Show class
	 * @param date - The time and date of the show.
	 * @param theater - The theater of the show.
	 * @param movie - The movie of the show.
	 */
	public Show(Date date,Theater theater, Movie movie, int adultPrice, int childPrice){
		this(date, theater, movie.getName(), movie.getGenre(), adultPrice, childPrice);
		this.myMovie = movie;
	}

	/**
	 * Constructor of the Show class
	 * @param xmlElement - Element of XML to initiate the class with
	 * @throws WrongXMLElement - If the element isn't belong to this class or corrupted.
	 */
	public Show(Theater theater, Element xmlElement) throws WrongXMLElement {
		this.myID = counter;
		counter++;
		this.theater = theater;
		initFromXml(xmlElement);
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#getMovieName()
	 */
	@Override
	public String getMovieName(){
		return movieName;
	}

	@Override
	public String getMovieGenre(){
		return movieGenre;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#getShowDate()
	 */
	@Override
	public Date getShowDate(){
		return date;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#freeSeats(java.lang.Integer[])
	 */
	@Override
	public void freeSeats(Integer[] seats) throws SeatAlreadyFree, IllegalSeatsFormat  {
		if (seats == null)
			throw new NullPointerException();
		if (seats.length < 2)
			throw new IllegalSeatsFormat();
		int row = seats[0];
		if (row >= this.seats.length || row < 0 || seats[1] < 0 || seats[seats.length - 1] >= this.seats[row].length)
			throw new IllegalSeatsFormat();
		for (int i = 1; i < seats.length; i++){
			if (this.seats[row][seats[i]] == false)
				throw new SeatAlreadyFree();
			this.seats[row][seats[i]] = false;
		}
	}

	/**
	 * The function returns if the show is expired
	 * @return 'true' if the show is expired. 'false' otherwise.
	 */
	public boolean IsExpired()
	{
		long now = System.currentTimeMillis();
		long timeLeft = date.getTime() - now;
		if (timeLeft< 0)
			return true;
		return false;
	}
	
	/**
	 * Private function. Returning available seats in the show.
	 * @param numberOfSeats - The number of seats that need be saved for the show.
	 * @return Array of integers representing the sites saved for the show. The first cell contain the row number
	 * 		   and the other cells contain the seats number
	 */
	private Integer[] LookForFreeSeats(int numberOfSeats) {
		if (numberOfSeats < 1)
			throw new IllegalArgumentException();
		Integer[] result = new Integer[numberOfSeats + 1];
		for (int i = 0; i < seats.length; i++){
			result[0] = i;	// The row.
			int place = 1;
			for (int j = 0; j < seats[i].length && place < result.length; j++){
				if (seats[i][j] == false){	// If the seat is free, try to add in to result.
					result[place] = j;
					place++;
				}
				else				// Start over if the seats is not close to each other
					place = 1;
			}
			if (place == result.length) {
				return result;		// Returns the result
			}
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#OrderPlaces(int)
	 */
	@Override
	public Integer[] OrderPlaces(int numberOfSeats) {
		Integer[] result = LookForFreeSeats(numberOfSeats);
		if (result != null)
			for (int j = 1; j < result.length; j++)		// Make all the seats of the result taken.
				seats[result[0]][result[j]] = true;
		return result;
	}


	/* (non-Javadoc)
	 * @see Cinema.CinemaEntities.IShow#checkOrderPossibility(int)
	 */
	@Override
	public Boolean checkOrderPossibility(int numberOfSeats) {
		Integer[] result = LookForFreeSeats(numberOfSeats);
		if (result == null)
			return false;
		return true;
	}

	public boolean checkEquals(Object obj) // For tests
	{
		if (!(obj instanceof Show)){
			return false;
		}
		Show show = (Show) obj;
		if (seats.length != show.seats.length|| theater.getRowAmount()!=show.theater.getRowAmount() ||
				theater.getRowSeatsAmount()!=show.theater.getRowSeatsAmount() || myMovie != show.myMovie || 
				adultPrice != show.adultPrice || childPrice != show.childPrice)
			return false;
		for (int i = 0; i < seats.length; i++){
			if (seats[i].length != show.seats[i].length)
				return false;
			for (int j = 0; j < seats[i].length; j++) {
				if (seats[i][j] != show.seats[i][j])
					return false;
			}
		}
		return true;
	}

	@Override
	public void initFromXml(Element xmlElement) throws WrongXMLElement {

		//verifying that write xml element given
		if (!xmlElement.getTagName().equals(XmlDefines.SHOW)){
			throw new WrongXMLElement();
		}

		//init movie name, genre, date, id
		movieName = XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.NAME).getTextContent();
		movieGenre = XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.GENRE).getTextContent();
		date = new Date( Long.parseLong(XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.DATE).getTextContent()));
		myID =  Long.parseLong(XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.SHOW_ID).getTextContent());
		
		//get price sub element for farther init of adult and child prices
		Element priceElement = XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.PRICE);
		//init adult and child prices
		String adultPriceString =
				XmlParseHelper.getFirstChildByName(priceElement, XmlDefines.ADULT_PRICE).getTextContent();
		adultPrice = Integer.parseInt(adultPriceString);
		String childPriceString =
				XmlParseHelper.getFirstChildByName(priceElement, XmlDefines.CHILD_PRICE).getTextContent();
		childPrice = Integer.parseInt(childPriceString);

		//creating seats mapping
		Element seatsElement = XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.SEATS_MATRIX);

		int rows = theater.getRowAmount();
		int columns = theater.getRowSeatsAmount();
		seats = new Boolean[rows][columns];

		ArrayList<Element> rowElements = XmlParseHelper.getChildrenByName(seatsElement, XmlDefines.ROW);
		for (int i=0 ; i<rows ; i++){

			String rowMapping =  rowElements.get(i).getTextContent();

			//restoring row mapping from a string of "1" and "0"
			for (int j=0; j<columns; j++){
				seats[i][j] = Integer.parseInt((rowMapping.substring(j, j+1))) == 1;
			}
		}

	}

	@Override
	public Element getXml(Document document) {

		Element showElement = document.createElement(XmlDefines.SHOW);

		//create field for movie name, time and genre and id
		XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.NAME, movieName);
		XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.GENRE, movieGenre);
		XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.DATE, new Long(date.getTime()).toString());
		XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.SHOW_ID, new Long(myID).toString());
		
		//create and append price element (and then adding 2 subelements)
		Element priceElement = XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.PRICE);
		XmlParseHelper.appendNewElementToParent(document, priceElement, XmlDefines.ADULT_PRICE, new Integer(adultPrice).toString());
		XmlParseHelper.appendNewElementToParent(document, priceElement, XmlDefines.CHILD_PRICE, new Integer(childPrice).toString());

		//create and save seats status
		Element seatsElement = XmlParseHelper.appendNewElementToParent(document, showElement, XmlDefines.SEATS_MATRIX);

		for (int i = 0; i<theater.getRowAmount(); i++){
			Element rowElement = document.createElement(XmlDefines.ROW);
			seatsElement.appendChild(rowElement);
			char[] seatsOfCurrebtRowMapping = new char [theater.getRowSeatsAmount()];

			//saving row mapping as a string of "1" and "0"
			for (int j=0; j<theater.getRowSeatsAmount(); j++){
				seatsOfCurrebtRowMapping[j] = (seats[i][j] ? "1" : "0").charAt(0);
			}
			rowElement.setTextContent(new String(seatsOfCurrebtRowMapping,0,seatsOfCurrebtRowMapping.length));

		}

		return showElement;
	}

	@Override
	public void initFromXml(boolean isRestorinf, Element xmlElement)
			throws WrongXMLElement {
		initFromXml(xmlElement);

	}

}
